goog.require('dkpoints.nav.Nav');
goog.require('dkpoints.tests.stubFn');


/*
 * Utility functions to easily mock dependencies.
 */

var realGoogHistory, fakeHistory = function() {};


/**
 * Get a mock dkpoints.nav.Path object with a fully mocked handler.
 * @return {Object} A mock dkpoints.nav.Path object.
 */
function getMockPath() {
  var handler = stubFn();
  handler.prototype.handle = stubFn();
  handler.prototype.transition = stubFn();

  return {
    handler: handler
  };
}


/**
 * Some tests test a static method on Nav, which we can access via
 * Nav.prototype.<method>.  When this is the case, we need to fill the nav
 * variable with something that has a history_ property with a dispose method
 * so tearDown doesn't freak out.
 */
function setMockNav() {
  nav = {
    history_: {
      dispose: function() {}
    }
  };
}


/**
 * Mock or un-mock goog.history.History
 * @param {boolean} mock Mocks History class if true; unmocks if false.
 * @param {function()=} constructor The mock constructor, if a custom one is
 *    needed.
 */
function mockGoogHistory(mock, constructor) {
  if (mock) {
    constructor = constructor || fakeHistory;
    if (goog.History !== constructor) {
      goog.History = constructor;
      goog.History.prototype = {
        setEnabled: function() {},
        dispose: function() {},
        addEventListener: function() {}
      };
    }
  }
  else {
    if (goog.History !== realGoogHistory) {
      goog.History = realGoogHistory;
    }
  }
}


var realGetHref;


/**
 * Mock dkpoints.utils.getLocationHref to return a specific URL.
 * @param {string=} newHref The URL to return when called, or blank to un-mock.
 */
function mockGetHref(newHref) {
  if (newHref) {
    dkpoints.utils.getLocationHref = function() {
      return newHref;
    };
  }
  else {
    dkpoints.utils.getLocationHref = realGetHref;
  }
}

var realGetHash;


/**
 * Mock dkpoints.utils.getLocationHash to return a specific URL hash.
 * @param {string=} newHash The hash to return when called, or blank to un-mock.
 */
function mockGetHash(newHash) {
  if (newHash) {
    dkpoints.utils.getLocationHash = function() {
      return newHash;
    };
  }
  else {
    dkpoints.utils.getLocationHash = realGetHash;
  }
}

var noAuth, normalAuth, adminAuth,
    bodyContent, navPrototype, realURLs, realRoute, realVerifyAuth;


var setUpPage = function() {
  navPrototype = dkpoints.nav.Nav.prototype;

  realURLs = dkpoints.urls;
  realGoogHistory = goog.History;
  realGetHref = dkpoints.utils.getLocationHref;
  realGetHash = dkpoints.utils.getLocationHash;
  realRoute = navPrototype.route_;
  realVerifyAuth = navPrototype.verifyAuth_;

  bodyContent = document.getElementById('body_content');

  noAuth = dkpoints.auth.AuthState.NO_AUTH;
  normalAuth = dkpoints.auth.AuthState.NORMAL_USER;
  adminAuth = dkpoints.auth.AuthState.ADMIN_USER;

  stubFn = dkpoints.tests.stubFn;
};


var sp, auth, rpc, status, nav, realNavigateTo;


var setUp = function() {
  auth = {
    getAuthStatus: goog.nullFunction,
    setNavManager: goog.nullFunction
  };
  rpc = {};
  status = {};
  sp = {
    getAuth: function() { return auth; },
    setNavManager: function(nav) { this.nav_ = nav; },
    setBaseURL: function(url) { this.baseURL_ = url; }
  };
};


/**
 * Test that creating a new dkpoints.nav.Nav creates a new history object.
 */
var testConstructorCreatesHistory = function() {
  var constructorCalled = false;
  mockGoogHistory(true, function() {
    constructorCalled = true;
  });
  nav = new dkpoints.nav.Nav(sp);
  assertTrue('Nav constructor should have called goog.History constructor',
             constructorCalled);
};


/*
 * Test that nav.baseURL_ is properly calculated (protocol + host:port + page;
 * no trailing slash), both locally and once deployed at a variety of URL
 * forms:
 *  - Local Bald: http://localhost:8080
 *  - Local Bald w/ Slash: http://localhost:8080/
 *  - Local Bald w/ Hash: http://localhost:8080#
 *  - Local Bald w/ Slash + Hash: http://localhost:8080/#
 *  - Local w/ Page: http://localhost:8080/#/raid
 *  - Local w/ Module + Page: http://localhost:8080/#/admin/raid
 *  - Local w/ Module + Page + Args: http://localhost:8080/#/admin/raid/523
 *  - Deployed Bald: http://dkpoints.appspot.com
 *  - Deployed Bald w/ Slash: http://dkpoints.appspot.com/
 *  - Deployed Bald w/ Hash: http://dkpoints.appspot.com#
 *  - Deployed Bald w/ Slash + Hash: http://dkpoints.appspot.com/#
 *  - Deployed w/ Page: http://dkpoints.appspot.com/#/raid
 *  - Deployed w/ Module + Page: http://dkpoints.appspot.com/#/admin/raid
 *  - Deployed w/ Module + Page + Args:
 *        http://dkpoints.appspot.com/#/admin/raid/523
 */


// Expected base URLs for the app when local and deployed.
var expectedLocalBaseURL = 'http://localhost:8080',
    expectedDeployedBaseURL = 'http://dkpoints.appspot.com';


/**
 * Test Base URL determination with a bald local URL.
 */
var testLocalBaseURLParse_Bald = function() {
  mockGoogHistory(true);
  mockGetHref(expectedLocalBaseURL);

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedLocalBaseURL,
               expectedLocalBaseURL, nav.baseURL_);
};


/**
 * Test Base URL determination with a local URL w/ a trailing '/'.
 */
var testLocalBaseURLParse_BaldSlash = function() {
  mockGoogHistory(true);
  mockGetHref(expectedLocalBaseURL + '/');

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedLocalBaseURL,
               expectedLocalBaseURL, nav.baseURL_);
};


/**
 * Test Base URL determination with a local URL w/ a trailing '#' (no '/').
 */
var testLocalBaseURLParse_BaldHash = function() {
  mockGoogHistory(true);
  mockGetHref(expectedLocalBaseURL + '#');

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedLocalBaseURL,
               expectedLocalBaseURL, nav.baseURL_);
};


/**
 * Test Base URL determination with a local URL w/ a trailing '/#'.
 */
var testLocalBaseURLParse_BaldSlashHash = function() {
  mockGoogHistory(true);
  mockGetHref(expectedLocalBaseURL + '/#');

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedLocalBaseURL,
               expectedLocalBaseURL, nav.baseURL_);
};


/**
 * Test Base URL determination with a local URL w/ a page token.
 */
var testLocalBaseURLParse_Page = function() {
  mockGoogHistory(true);
  mockGetHref(expectedLocalBaseURL + '/#/raid');

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedLocalBaseURL,
               expectedLocalBaseURL, nav.baseURL_);
};


/**
 * Test Base URL determination with a local URL w/ a module + page token.
 */
var testLocalBaseURLParse_ModulePage = function() {
  mockGoogHistory(true);
  mockGetHref(expectedLocalBaseURL + '/#/admin/raid');

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedLocalBaseURL,
               expectedLocalBaseURL, nav.baseURL_);
};


/**
 * Test Base URL determination with a local URL w/ a module + page token.
 */
var testLocalBaseURLParse_ModulePageArgs = function() {
  mockGoogHistory(true);
  mockGetHref(expectedLocalBaseURL + '/#/admin/raid/532');

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedLocalBaseURL,
               expectedLocalBaseURL, nav.baseURL_);
};


/*
 * Test deployed base URLs.
 */


/**
 * Test Base URL determination with a bald deployed URL.
 */
var testDeployedBaseURLParse_Bald = function() {
  mockGoogHistory(true);
  mockGetHref(expectedDeployedBaseURL);

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedDeployedBaseURL,
               expectedDeployedBaseURL, nav.baseURL_);
};


/**
 * Test Base URL determination with a deployed URL w/ a trailing '/'.
 */
var testDeployedBaseURLParse_BaldSlash = function() {
  mockGoogHistory(true);
  mockGetHref(expectedDeployedBaseURL + '/');

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedDeployedBaseURL,
               expectedDeployedBaseURL, nav.baseURL_);
};


/**
 * Test Base URL determination with a deployed URL w/ a trailing '#' (no '/').
 */
var testDeployedBaseURLParse_BaldHash = function() {
  mockGoogHistory(true);
  mockGetHref(expectedDeployedBaseURL + '#');

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedDeployedBaseURL,
               expectedDeployedBaseURL, nav.baseURL_);
};


/**
 * Test Base URL determination with a deployed URL w/ a trailing '/#'.
 */
var testDeployedBaseURLParse_BaldSlashHash = function() {
  mockGoogHistory(true);
  mockGetHref(expectedDeployedBaseURL + '/#');

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedDeployedBaseURL,
               expectedDeployedBaseURL, nav.baseURL_);
};


/**
 * Test Base URL determination with a deployed URL w/ a page token.
 */
var testDeployedBaseURLParse_Page = function() {
  mockGoogHistory(true);
  mockGetHref(expectedDeployedBaseURL + '/#/raid');

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedDeployedBaseURL,
               expectedDeployedBaseURL, nav.baseURL_);
};


/**
 * Test Base URL determination with a deployed URL w/ a module + page token.
 */
var testDeployedBaseURLParse_ModulePage = function() {
  mockGoogHistory(true);
  mockGetHref(expectedDeployedBaseURL + '/#/admin/raid');

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedDeployedBaseURL,
               expectedDeployedBaseURL, nav.baseURL_);
};


/**
 * Test Base URL determination with a deployed URL w/ a module + page token.
 */
var testDeployedBaseURLParse_ModulePageArgs = function() {
  mockGoogHistory(true);
  mockGetHref(expectedDeployedBaseURL + '/#/admin/raid/532');

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('nav.baseURL_ should equal ' + expectedDeployedBaseURL,
               expectedDeployedBaseURL, nav.baseURL_);
};


/*
 * End Base URL tests.
 */


/**
 * Test that changing the hash will cause a Navigate event
 */
var testHashChangeHandlesOneNavEvent = function() {
  mockGoogHistory(false);

  // navPrototype is the prototype of my navigation manager, aliased for speed.
  // verifyAuth is called from the nav event handling method, so it's a good
  // hook to see if that method was called or not.

  var route = navPrototype.route_ = stubFn();

  nav = new dkpoints.nav.Nav(sp);
  assertEquals('Nav event should have been fired on History object creation.',
               1, route.timesCalled);

  // Apparently Opera takes a long time with this test, so give it a long
  // timeout.
  waitForTimeout(function() {
    assertEquals('Changing the hash token should cause exactly 1 nav callback',
                 2, route.timesCalled);
    navPrototype.verifyAuth_ = realVerifyAuth;
  }, 1000);

  var hash = 'TestHash',
      currentHash = location.hash,
      index = parseInt((currentHash.match(/#TestHash(\d)*/) || [0, 0])[1]) + 1;

  location.hash = hash + index;
};


/*
 * Tests for dkpoints.nav.Nav.prototype.disectPath.
 * Test for proper behavior when input is:
 * - Single page path + no params
 * - Single page path + 1 param
 * - Single page path + arbitrary params
 * - Multiple page path + no params
 * - Multiple page path + 1 param
 * - Multiple page path + arbitrary params
 *
 * Also test for paths that should not match anything in dkpoints.urls.
 */


/**
 * Test that disectPath works with an empty string.
 */
var testDisectPathWithEmptyString = function() {
  var realURLs = dkpoints.urls;

  setMockNav();
  var handler = function() {};
  dkpoints.urls = {
    '': handler
  };

  var path = navPrototype.disectPath('');
  assertEquals('path.handler should be the one we mapped to the emtpy string.',
               handler, path.handler);

  dkpoints.urls = realURLs;
};


/**
 * Test paths that should not match anything in dkpoints.urls.
 */
var testDisectPathWithNonMatchingInput = function() {
  setMockNav();

  var handler = function() {};

  var badHashes = [
    '/rade',
    '/admins/rai',
    '/rai',
    '/itemid',
    '/shoes',
    '/not/in/dkpoints/urls'
  ];

  for (var i = 0, len = badHashes.length; i < len; ++i) {
    var hash = badHashes[i],
        resultPath = navPrototype.disectPath(hash);

    assertEquals('Hash should be ' + hash + '.', hash, resultPath.hash);
    assertEquals('Path should be an empty string.', '', resultPath.path);
    assertEquals('Params should be an empty string.', '', resultPath.params);
    assertUndefined('Handler should be undefined for hash ' + badHashes[i],
                    resultPath.handler);
  }
};


/**
 * Test disectPath method with single page path
 */
var testDisectPathWithSinglePagePath = function() {
  setMockNav();

  var raidHandler = function() {};
  dkpoints.urls = {
    '/raid': raidHandler
  };

  var hash = '/raid',
      expectedPath = '/raid',
      expectedParams = '',
      resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);

  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be an empty string.', '', resultPath.params);
  assertTrue('Handler should be our mock raidHandler function',
             resultPath.handler === raidHandler);

  // Test hash with trailing slash.
  hash += '/';
  resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);

  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be an empty string.', '', resultPath.params);
  assertTrue('Handler should be our mock raidHandler function',
             resultPath.handler === raidHandler);
};


/**
 * Test disectPath method with a single page path + 1 param.
 */
var testDisectPathWithSinglePagePathAndOneParam = function() {
  setMockNav();

  var raidHandler = function() {};
  dkpoints.urls = {
    '/raid': raidHandler
  };

  var hash = '/raid/2353',
      expectedPath = '/raid',
      expectedParams = '2353',
      resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);

  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertTrue('Handler should be our mock raidHandler function',
             resultPath.handler === raidHandler);

  // Test hash with trailing slash.
  hash += '/';
  resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);

  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertTrue('Handler should be our mock raidHandler function',
             resultPath.handler === raidHandler);
};


/**
 * Test disectPath method with single page path + arbitrary params.
 */
var testDisectPathWithSinglePagePathAndArbitraryParams = function() {
  setMockNav();

  var raidHandler = function() {};
  dkpoints.urls = {
    '/raid': raidHandler
  };

  var hash = '/raid/search/Tuesdays',
      expectedPath = '/raid',
      expectedParams = 'search/Tuesdays',
      resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);

  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertTrue('Handler should be our mock raidHandler function',
             resultPath.handler === raidHandler);

  // Test hash with trailing slash.
  hash += '/';
  resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);

  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertTrue('Handler should be our mock raidHandler function',
             resultPath.handler === raidHandler);
};


/**
 * Test disectPath method with multiple page path + no params.
 */
var testDisectPathWithMultiplePagePath = function() {
  setMockNav();

  var raidHandler = function() {'raidHandler'},
      adminHandler = function() {'adminHandler'},
      adminRaidHandler = function() {'adminRaidHandler'};
  dkpoints.urls = {
    '/admin': adminHandler,
    '/admin/raid': adminRaidHandler,
    '/raid': raidHandler
  };

  // Test for a match against a multi-page mapped URL (/admin/raid)
  var hash = '/admin/raid',
      expectedPath = '/admin/raid',
      expectedParams = '',
      resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);

  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertEquals('Handler should be our mock adminRaidHandler function',
               adminRaidHandler, resultPath.handler);

  // Test for a match against a single-page mapped URL ('/admin')
  delete dkpoints.urls['/admin/raid'];

  expectedPath = '/admin';
  expectedParams = 'raid';
  resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);
  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertEquals('Handler should be our mock adminHandler function',
               adminHandler, resultPath.handler);

  // Test hash with trailing slash.
  hash += '/';
  resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);
  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertEquals('Handler should be our mock adminHandler function',
               adminHandler, resultPath.handler);
};


/**
 * Test disectPath method with multiple page path + 1 param.
 */
var testDisectPathWithMultiplePagePathAndOneParam = function() {
  setMockNav();

  var raidHandler = function() {'raidHandler'},
      adminHandler = function() {'adminHandler'},
      adminRaidHandler = function() {'adminRaidHandler'};
  dkpoints.urls = {
    '/admin': adminHandler,
    '/admin/raid': adminRaidHandler,
    '/raid': raidHandler
  };

  // Test for a match against a multi-page mapped URL (/admin/raid)
  var hash = '/admin/raid/2353',
      expectedPath = '/admin/raid',
      expectedParams = '2353',
      resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);

  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertEquals('Handler should be our mock adminRaidHandler function',
               adminRaidHandler, resultPath.handler);

  // Test for a match against a single-page mapped URL ('/admin')
  delete dkpoints.urls['/admin/raid'];

  expectedPath = '/admin';
  expectedParams = 'raid/2353';
  resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);
  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertEquals('Handler should be our mock adminHandler function',
               adminHandler, resultPath.handler);

  // Test hash with trailing slash.
  hash += '/';
  resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);
  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertEquals('Handler should be our mock adminHandler function',
               adminHandler, resultPath.handler);
};


/**
 * Test disectPath method with multiple page path + arbitrary
 */
var testDisectPathWithMultiplePagePathAndArbitraryParams = function() {
  setMockNav();

  var raidHandler = function() {'raidHandler'},
      adminHandler = function() {'adminHandler'},
      adminRaidHandler = function() {'adminRaidHandler'};
  dkpoints.urls = {
    '/admin': adminHandler,
    '/admin/raid': adminRaidHandler,
    '/raid': raidHandler
  };

  // Test for a match against a multi-page mapped URL (/admin/raid)
  var hash = '/admin/raid/search/Tuesday',
      expectedPath = '/admin/raid',
      expectedParams = 'search/Tuesday',
      resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);

  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertEquals('Handler should be our mock adminRaidHandler function',
               adminRaidHandler, resultPath.handler);

  // Test for a match against a single-page mapped URL ('/admin')
  delete dkpoints.urls['/admin/raid'];

  expectedPath = '/admin';
  expectedParams = 'raid/search/Tuesday';
  resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);
  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertEquals('Handler should be our mock adminHandler function',
               adminHandler, resultPath.handler);


  // Test hash with trailing slash.
  hash += '/';
  resultPath = dkpoints.nav.Nav.prototype.disectPath(hash);
  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertEquals('Handler should be our mock adminHandler function',
               adminHandler, resultPath.handler);
};


/**
 * Paths mapped to null handlers are used to denote not-yet-implemented
 * handlers.  Make sure that disectPath correctly finds these mappings and
 * properly sets the path and params parameters.
 */
var testDisectPathFindsNullHandlerMappings = function() {
  setMockNav();

  dkpoints.urls['/test/path'] = null;

  var hash = '/test/path/params/5/234',
      expectedPath = '/test/path',
      expectedParams = 'params/5/234';

  var resultPath = navPrototype.disectPath(hash);
  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertNull('Handler should be null', resultPath.handler);

  // Test hash with trailing slash.
  hash += '/';
  var resultPath = navPrototype.disectPath(hash);
  assertEquals('Hash should be ' + hash, hash, resultPath.hash);
  assertEquals('Path should be ' + expectedPath, expectedPath, resultPath.path);
  assertEquals('Params should be ' + expectedParams,
               expectedParams, resultPath.params);
  assertNull('Handler should be null', resultPath.handler);

  delete dkpoints.urls['/test/path'];
};


/**
 * Test that route_ correctly sets nav.page_, referer_, and backtrackReferer_.
 */
var testRouteTransitionsPageReferers = function() {
  mockGoogHistory(true);

  var realVerifyAuth = navPrototype.verifyAuth_;
  navPrototype.verifyAuth_ = goog.nullFunction;

  nav = new dkpoints.nav.Nav(sp);

  assertEquals('page_ should default to an empty string.', '', nav.page_.hash);
  assertEquals('referer_ should default to an empty string.',
               '', nav.referer_.hash);
  assertEquals('backtrackReferer_ should default to an empty string.',
               '', nav.backtrackReferer_.hash);

  var firstToken = '/fake/path';
  nav.route_({token: firstToken});
  assertEquals('page_ should now be ' + firstToken,
               firstToken, nav.page_.hash);
  assertEquals('referer_ should still be an empty string.',
               '', nav.referer_.hash);
  assertEquals('backtrackReferer_ should still be an empty string.',
               '', nav.backtrackReferer_.hash);

  var secondToken = '/wrong/path';
  nav.route_({token: secondToken});
  assertEquals('page_ should now be ' + secondToken,
               secondToken, nav.page_.hash);
  assertEquals('referer_ should now be ' + firstToken,
               firstToken, nav.referer_.hash);
  assertEquals('backtrackReferer_ should still be an empty string.',
               '', nav.backtrackReferer_.hash);

  var thirdToken = '/unreal/path';
  nav.route_({token: thirdToken});
  assertEquals('page_ should now be ' + thirdToken,
               thirdToken, nav.page_.hash);
  assertEquals('referer_ should now be ' + secondToken,
               secondToken, nav.referer_.hash);
  assertEquals('backtrackReferer_ should now be ' + firstToken,
               firstToken, nav.backtrackingReferer_.hash);

  navPrototype.verifyAuth_ = realVerifyAuth;
};


/**
 * Test that route_ correctly sets nav.page_ and referer_ on rollback.
 */
var testRouteTransitionsPageReferesOnRollback = function() {
  mockGoogHistory(true);

  var realVerifyAuth = navPrototype.verifyAuth_;
  navPrototype.verifyAuth_ = goog.nullFunction;

  nav = new dkpoints.nav.Nav(sp);

  assertEquals('page_ should default to an empty string.',
               '', nav.page_.hash);
  assertEquals('referer_ should default to an empty string.',
               '', nav.referer_.hash);
  assertEquals('backtrackReferer_ should default to an empty string.',
               '', nav.backtrackReferer_.hash);

  var firstToken = '/fake/path';
  nav.route_({token: firstToken});
  assertEquals('page_ should now be ' + firstToken,
               firstToken, nav.page_.hash);
  assertEquals('referer_ should still be an empty string.',
               '', nav.referer_.hash);
  assertEquals('backtrackReferer_ should still be an empty string.',
               '', nav.backtrackReferer_.hash);

  navPrototype.verifyAuth_ = realVerifyAuth;
};


/**
 * Test that Nav.route_ calls auth.getAuthStatus if nav.authStatus_ is null.
 */
var testRouteCallsGetAuthStatusWhenAuthStatusIsNull = function() {
  mockGoogHistory(true);

  var verifyAuth = navPrototype.verifyAuth_ = stubFn(),
      getStatus = auth.getAuthStatus = stubFn();

  nav = new dkpoints.nav.Nav(sp);

  nav.route_({token: '/admin/raid'});
  assertTrue('getAuthStatus should have been called when nav.authStatus_ is ' +
             'null.', getStatus.called);
  assertFalse('verifyAuth should not have been called when nav.authStatus_ is' +
              ' null', verifyAuth.called);
};


/**
 * Test that Nav.route_ calls nav.verifyAuth_ if nav.authStatus_ is non-null.
 */
var testRouteCallsVerifyAuthWhenAuthStatusIsNonNull = function() {
  mockGoogHistory(true);

  var verifyAuth = navPrototype.verifyAuth_ = stubFn(),
      getStatus = auth.getAuthStatus = stubFn(),
      authStatus = {};

  nav = new dkpoints.nav.Nav(sp);

  nav.authStatus_ = authStatus;
  nav.route_({token: '/admin/raid'});
  assertTrue('verifyAuth should have been called when nav.authStatus_ is ' +
             'null', verifyAuth.called);
  assertFalse('getAuthStatus should not have been called when ' +
              'nav.authStatus_ is null.', getStatus.called);
};


/**
 * Test that verifyAuth_ sets handler to dkpoints.urls[':401'] if the handler
 * requires a higher auth level than the user has.
 */
var testVerifyAuthSets403OnUnderAuthorization = function() {
  mockGoogHistory(true);

  var realUnauthHandler = dkpoints.urls[':501'],
      realActivateHandler = navPrototype.activateHandler_,
      path = {
        hash: '/admin/raid/5',
        path: '/admin/raid',
        params: '5',
        handler: function() {}
      },
      unauthHandler = dkpoints.urls[':403'] = function() {},
      verifiedPath = null;

  navPrototype.activateHandler_ = function(path) {
    verifiedPath = path;
  };

  nav = new dkpoints.nav.Nav(sp);

  // Test for admin required when unauthorized.
  path.handler.requiredAuth = adminAuth;

  nav.verifyAuth_(path, {getState: function() { return noAuth; }});
  assertEquals('verifyAuth should have set path.handler to urls[:501].',
               unauthHandler, verifiedPath.handler);

  nav.verifyAuth_(path, {getState: function() { return noAuth; }});
  assertEquals('verifyAuth should have set path.handler to urls[:403].',
               unauthHandler, verifiedPath.handler);

  // Test for normal user required when unauthorized.
  path.handler.requiredAuth = normalAuth;

  nav.verifyAuth_(path, {getState: function() { return noAuth; }});
  assertEquals('verifyAuth should have set path.handler to urls[:403].',
               unauthHandler, verifiedPath.handler);

  nav.verifyAuth_(path, {getState: function() { return noAuth; }});
  assertEquals('verifyAuth should have set path.handler to urls[:403].',
               unauthHandler, verifiedPath.handler);

  // Test for admin required when normal user
  path.handler.requiredAuth = adminAuth;

  nav.verifyAuth_(path, {getState: function() { return normalAuth; }});
  assertEquals('verifyAuth should have set path.handler to urls[:403].',
               unauthHandler, verifiedPath.handler);

  dkpoints.urls[':501'] = realUnauthHandler;
  navPrototype.activateHandler_ = realActivateHandler;
};


/**
 * Test that verifyAuth_ allows handlers when the user has the required auth
 * status.
 */
var testVerifyAuthAllowsHandlerWhenUserHasRequiredAuth = function() {
  mockGoogHistory(true);

  var realActivateHandler = navPrototype.activateHandler_,
      path = {
        hash: '/admin/raid/5',
        path: '/admin/raid',
        params: '5',
        handler: function() {}
      },
      verifiedPath = null,
      handler = path.handler;

  navPrototype.activateHandler_ = function(path) {
    verifiedPath = path;
  };

  nav = new dkpoints.nav.Nav(sp);

  // Test for no auth required while unauthorized
  path.handler.requiredAuth = noAuth;

  nav.verifyAuth_(path, {getState: function() { return noAuth; }});
  assertEquals('verifyAuth should allow handler when no auth is required ' +
               'and user is unauthorized.', handler, verifiedPath.handler);

  // Test for no auth required while normal user
  nav.verifyAuth_(path, {getState: function() { return normalAuth; }});
  assertEquals('verifyAuth should allow handler when no auth is required ' +
               'and user is a normal user.', handler, verifiedPath.handler);

  // Test for no auth required while admin
  nav.verifyAuth_(path, {getState: function() { return adminAuth; }});
  assertEquals('verifyAuth should allow handler when no auth is required ' +
               'and user is an admin.', handler, verifiedPath.handler);

  // Test for normal auth required while normal user
  path.handler.requiredAuth = normalAuth;

  nav.verifyAuth_(path, {getState: function() { return normalAuth; }});
  assertEquals('verifyAuth should allow handler when normal auth is required ' +
               'and user is a normal user.', handler, verifiedPath.handler);

  // Test for normal auth required while admin
  nav.verifyAuth_(path, {getState: function() { return adminAuth; }});
  assertEquals('verifyAuth should allow handler when normal auth is required ' +
               'and user is an admin.', handler, verifiedPath.handler);

  // Test for admin required while admin
  path.handler.requiredAuth = adminAuth;

  nav.verifyAuth_(path, {getState: function() { return adminAuth; }});
  assertEquals('verifyAuth should allow handler when normal auth is required ' +
               'and user is an admin.', handler, verifiedPath.handler);
};


/**
 * Test that verifyAuth calls activateHandler_ with isTransition = false when
 * there is no pre-existing handler (nav.activeHandler_ === null).
 */
var testVerifyAuthCallsActivateHandlerProperlyWhenNoActiveHandler = function() {
  mockGoogHistory(true);

  var transitionToggled = null,
      realActivateHandler = navPrototype.activateHandler_,
      path = {handler: {requiredAuth: noAuth}},
      givenPath = null;

  navPrototype.activateHandler_ = function(path, isTransition) {
    givenPath = path;
    transitionToggled = isTransition;
  };

  nav = new dkpoints.nav.Nav(sp);
  nav.verifyAuth_(path, {getState: function() { return noAuth; }});

  assertEquals('ActiveHandler should have been passed the path object.',
               path, givenPath);
  assertFalse('ActiveHandler should have been passed isTransition = false.',
              transitionToggled);

  navPrototype.activateHandler_ = realActivateHandler;
};


/**
 * Test that verifyAuth calls exit on the active handler if the new one is
 * not the same type.
 */
var testVerifyAuthCallsActiveHandlerExitForDifferentHandler = function() {
  mockGoogHistory(true);

  var exitCalled = false,
      onExitCalled = false,
      path = {
        handler: function() { 'New Handler'; }
      };
  path.handler.requiredAuth = noAuth;

  nav = new dkpoints.nav.Nav(sp);
  nav.activeHandler_ = {
    exit: function(onExit) {
      exitCalled = true;
      onExit();
    }
  };

  var realOnExit = navPrototype.onExit_;
  navPrototype.onExit_ = function() {
    onExitCalled = true;
  };

  nav.verifyAuth_(path, {getState: function() { return noAuth; }});
  assertTrue('ActiveHandler.exit should have been called.', exitCalled);
  assertTrue('VerifyAuth should have passed a bound onExit_ reference to ' +
             'activeHandler.exit.', onExitCalled);

  navPrototype.onExit_ = realOnExit;
};


/**
 * Test that if nav.activeHandler_ is an instance of path.handler, verifyAuth:
 * - does NOT call nav.activeHandler_.exit()
 * - does call nav.activateHandler_ with isTransition === true
 */
var testVerifyAuthDoesNotExitActiveHandlerIfSameHandler = function() {
  mockGoogHistory(true);

  var exitCalled = false,
      transitionToggled = false,
      handler = function() { 'Constructor'; },
      path = {
        handler: handler
      },
      givenPath = null;

  handler.requiredAuth = noAuth;
  handler.prototype.exit = function() { exitCalled = true; };

  var realActivateHandler = navPrototype.activateHandler_;
  navPrototype.activateHandler_ = function(path, isTransition) {
    givenPath = path;
    transitionToggled = isTransition;
  };

  nav = new dkpoints.nav.Nav(sp);
  nav.activeHandler_ = new handler();

  nav.verifyAuth_(path, {getState: function() { return noAuth; }});
  assertFalse('ActiveHandler.exit should not have been called.',
              exitCalled);
  assertEquals('ActivateHandler should have been passed the path object.',
               path, givenPath);
  assertTrue("ActivateHandler should've been called with isTransition = true.",
             transitionToggled);

  navPrototype.activateHandler_ = realActivateHandler;
};


/**
 * Test that nav.onExit(path, true) calls activateHandler_(path).
 */
var testOnExitCallsActivateHandlerOnExitComplete = function() {
  setMockNav();

  var path = {},
      activateHandler = nav.activateHandler_ = stubFn(),
      rollback = nav.rollback_ = stubFn();

  // Run the real onExit_ against our mock nav.
  navPrototype.onExit_.call(nav, path, true);

  assertTrue('activateHandler_ should have been called.',
             activateHandler.called);
  assertEquals('activateHandler_ should have been given the path object.',
               path, activateHandler.args[0]);
  assertFalse('rollback_ should not have been called.', rollback.called);
};


/**
 * Test that nav.onExit_(path, false) calls rollback_();
 */
var testOnExitCallsRollbackOnExitFalse = function() {
  setMockNav();

  var path = {},
      activateHandler = nav.activateHandler_ = stubFn(),
      rollback = nav.rollback_ = stubFn();

  // Run the real onExit against our mock nav.
  navPrototype.onExit_.call(nav, path, false);

  assertFalse('activateHandler_ should not have been called.',
              activateHandler.called);
  assertTrue('rollback_ should have been called.', rollback.called);
};


/**
 * Test that activateHandler_ creates a new instance of path.handler if
 * path.handler.instance does not exist and passes in the ServiceProvider.
 */
var testActivateHandlerCreatesHandlerIfNonExistent = function() {
  mockGoogHistory(true);

  var path = getMockPath();

  nav = new dkpoints.nav.Nav(sp);
  nav.activateHandler_(path, false);

  var handler = path.handler;
  assertTrue('path.handler should have been called.', handler.called);
  assertEquals('path.handler should have been given the service provider',
               sp, handler.args[0]);
  assertTrue('activateHandler should have saved an instance of handler ' +
             'onto the path.handler reference.',
             path.handler.instance instanceof handler);
  assertEquals('nav.activeHandler_ should be a reference to ' +
               'path.handler.instance',
               path.handler.instance, nav.activeHandler_);
};


/**
 * Test that activateHandler_ does not create a new handler if one already
 * exists.
 */
var testActivateHandlerReusesExistingHandler = function() {
  mockGoogHistory(true);

  var path = getMockPath(),
      handler = path.handler,
      instance = handler.instance = new path.handler();

  // Reset stub flags.
  handler.called = false;
  handler.args = null;

  nav = new dkpoints.nav.Nav(sp);
  nav.activateHandler_(path, false);

  assertFalse('path.handler should not have been called.', handler.called);
  assertEquals('activateHandler should not have reset path.handler.instance.',
               instance, handler.instance);
};


/**
 * Test that activateHandler_ calls handler.handle when isTransition === false.
 */
var testActivateHandlerCallsHandleWhenTransitionIsFalse = function() {
  mockGoogHistory(true);

  var path = getMockPath();

  nav = new dkpoints.nav.Nav(sp);
  nav.activateHandler_(path, false);

  var handle = path.handler.prototype.handle;
  var transition = path.handler.prototype.transition;

  assertTrue('handler.handle should have been called.', handle.called);
  assertEquals('handler.handle should have been given the path object.',
               path, handle.args[0]);
  assertFalse('handler.transition should not have been called.',
              transition.called);
};


/**
 * Test that activateHandler_ calls handler.transition when
 * isTransition === true.
 */
var testActivateHandlerCallsTransitionWhenTransitionIsTrue = function() {
  mockGoogHistory(true);

  var realRollback = navPrototype.rollback_,
      path = getMockPath();

  var rollback = navPrototype.rollback_ = stubFn();

  nav = new dkpoints.nav.Nav(sp);
  nav.activateHandler_(path, true);

  var handle = path.handler.prototype.handle;
  var transition = path.handler.prototype.transition;

  // Transition.arguments[1] should be our mock rollback method.
  // Since it'll be goog.bind'ed, we'll need to execute it and see if it was
  // called since we can't check identity equality.
  transition.args[1]();

  assertTrue('handler.transition should have been called.', transition.called);
  assertEquals('handler.transition should have been given the path object.',
               path, transition.args[0]);
  assertTrue('handler.transition should have been given this.rollback_ ' +
             'as the handleCancelled callback.', rollback.called);
  assertFalse('handler.handle should not have been called.', handle.called);

  navPrototype.rollback_ = realRollback;
};


/**
 * Test that calling setAuthStatus with stores the user's auth status.
 */
var testSetAuthStatus = function() {
  mockGoogHistory(true);
  nav = new dkpoints.nav.Nav(sp);

  // Calling with no argument or null should set nav.authStatus_ to null.
  nav.setAuthStatus();
  assertNull('Nav.authStatus_ should be null for no argument.',
             nav.authStatus_);

  nav.setAuthStatus(null);
  assertNull('Nav.authStatus_ should be null when called with null.',
             nav.authStatus_);

  var authStatus = {};
  nav.setAuthStatus(authStatus);
  assertEquals('Nav.authStatus_ should be the object we gave it.',
               authStatus, nav.authStatus_);
};


var tearDown = function() {
  nav.history_.dispose();
  nav = null;

  mockGoogHistory(false);
  mockGetHash();
  mockGetHref();

  location.hash = '';

  dkpoints.urls = realURLs;
  bodyContent.innerHTML = '';
  navPrototype.route_ = realRoute;
  navPrototype.verifyAuth_ = realVerifyAuth;
};


// var tearDownPage = function() {};


var testCase = new goog.testing.ContinuationTestCase(document.title);
testCase.autoDiscoverTests();
G_testRunner.initialize(testCase);
